﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.IO;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Zip;

namespace KC.DataAccess.Compression
{
	public class Zip
	{
		/// <summary>Zips files.</summary>
		/// <param name="inputFolderPath">Zip all files in this directory</param>
		/// <param name="outputPathAndFile">Fully qualified ZIP file to generate</param>
		/// <param name="password">Password to apply to ZIP file</param>
		public async static void ZipFiles(string inputFolderPath, string outPath, string password = null)
		{
			if (string.IsNullOrEmpty(inputFolderPath)) throw new ArgumentNullException("inputFolderPath");
			if (string.IsNullOrEmpty(outPath)) throw new ArgumentNullException("outputPathAndFile");
				ArrayList filenames = await BuildFileList(inputFolderPath);

				Task t2 = new Task(() => {
					int TrimLength = (Directory.GetParent(inputFolderPath)).ToString().Length + 1;
					FileStream inStream;
					byte[] obuffer;
					ZipOutputStream outStream = new ZipOutputStream(File.Create(outPath));
					if (password != null && password != String.Empty) outStream.Password = password;
					outStream.SetLevel(9);
					ZipEntry zipEntry;

					foreach (string inFilename in filenames) {
						zipEntry = new ZipEntry(inFilename.Remove(0, TrimLength));
						outStream.PutNextEntry(zipEntry);
						if (!inFilename.EndsWith(@"/")) {	// Handle directories
							inStream = File.OpenRead(inFilename);
							obuffer = new byte[inStream.Length];
							inStream.Read(obuffer, 0, obuffer.Length);
							outStream.Write(obuffer, 0, obuffer.Length); } }

					outStream.Finish();
					outStream.Close(); });
				t2.Start();
				t2.Wait(); 
		}
		/// <summary>Unzips files.</summary>
		/// <param name="zipFilename">Fully qualified filename to unzip</param>
		/// <param name="deleteZipFile">Delete the ZIP when done?</param>
		/// <param name="outputFolder">Directory to unzip to. If null, unzips to same directory</param>
		/// <param name="password">Password to unlock ZIP file</param>
		public async static void UnZipFiles(string zipFilename, bool deleteZipFile, string outputFolder = null, string password = null)
		{
			// Validation
			await Task.Run(() => {
				if (string.IsNullOrEmpty(zipFilename)) throw new ArgumentNullException("zipFilename");
				if (!System.IO.File.Exists(zipFilename)) throw new FileNotFoundException("File not found: " + zipFilename);
				if (string.IsNullOrEmpty(outputFolder)) outputFolder = new FileInfo(zipFilename).DirectoryName + Path.GetFileNameWithoutExtension(zipFilename);
				if (!Directory.Exists(outputFolder))
					try { Directory.CreateDirectory(outputFolder); }
					catch { outputFolder = new FileInfo(zipFilename).DirectoryName; }
				if (!outputFolder.EndsWith("\\")) outputFolder += "\\"; });

			await Task.Run(() => {
				ZipInputStream inStream = new ZipInputStream(File.OpenRead(zipFilename));
				if (!string.IsNullOrEmpty(password))
					inStream.Password = password;

				ZipEntry zipEntry;
				while ((zipEntry = inStream.GetNextEntry()) != null) {
					string fileName = Path.GetFileName(zipEntry.Name);
					if (fileName != String.Empty /* && zipEntry.Name.IndexOf(".ini") < 0 */) {
						string
							OutputFile = outputFolder + zipEntry.Name.Replace("\\ ", "\\"),
							fullDirPath = Path.GetDirectoryName(OutputFile);
						if (!Directory.Exists(fullDirPath)) Directory.CreateDirectory(fullDirPath);

						FileStream streamWriter = File.Create(OutputFile);
						int size = 2048;
						byte[] data = new byte[2048];
						while (true) {
							size = inStream.Read(data, 0, data.Length);
							if (size > 0) streamWriter.Write(data, 0, size);
							else break; }
						streamWriter.Close(); } }
				inStream.Close();
				if (deleteZipFile)
					File.Delete(zipFilename); });
		}
		/// <summary>Builds a recursive list of fully-qualified local filenames in a given directory</summary>
		/// <param name="Dir">Directory to grep files from</param>
		/// <returns>List of fully-qualified filenames</returns>
		private async static Task<ArrayList> BuildFileList(string Dir)
		{
			ArrayList filenames = new ArrayList();
			foreach (string file in Directory.GetFiles(Dir)) filenames.Add(file);
			if (filenames.Count == 0 && Directory.GetDirectories(Dir).Length == 0) filenames.Add(Dir + @"/");
			foreach (string dirs in Directory.GetDirectories(Dir)) {
				var obj2 = await Task.Run(() => BuildFileList(dirs));
				foreach (object obj in obj2)
					filenames.Add(obj); } 
			return filenames;
		}
	}
}
